package sketch.generator;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import randoop.util.Reflection;
import sketch.generator.examples.SequentialValue;
import sketch.util.Checker;
import sketch.util.SketchUtil;

public class ExhaustiveTypedValueGenerator<T> extends CompositeGenerator<SequentialValue<T>> {

	private final Class<?>[] types;
	
	private final Map<Class<?>, Set<T>> classifiedValues;
	
	public final List<IGenerator<T>> generators;
	
	//two methods are redundant
	public ExhaustiveTypedValueGenerator(Class<?>[] types, List<T> values) {
		super();
		Checker.checkNull(types, "The types input could not be null.");
		Checker.checkNull(values, "The values input could not be null");
		this.types = types;
		classifiedValues = new LinkedHashMap<Class<?>, Set<T>>();
		for(Class<?> type : types) {
			Set<T> list = null;
			if(classifiedValues.containsKey(type)) {
				list = this.classifiedValues.get(type);
			} else {
				list = new LinkedHashSet<T>();
				classifiedValues.put(type, list);
			}
			for(T value : values) {
				if (value == null) {
					if(SketchUtil.isPrimitiveOrBoxingType(type)) {
						continue;
					} else {
						list.add(value);
					}
				} else if(Reflection.canBeUsedAs(value.getClass(), type)) {
					list.add(value);
				}
			}
			
			Checker.checkTrue(!list.isEmpty(), "There is no value for type: " + type);
		}
		//generators
		this.generators = new ArrayList<IGenerator<T>>();
		for(Class<?> type : types) {
			Set<T> set = this.classifiedValues.get(type);
			//List l
			IGenerator<T> chainGenerator = new ChainValueGenerator<T>(set);
			this.generators.add(chainGenerator);
		}
	}
	
	public ExhaustiveTypedValueGenerator(Class<?>[] types, T... values) {
		super();
		Checker.checkNull(types, "The types input could not be null.");
		Checker.checkNull(values, "The values input could not be null");
		this.types = types;
		classifiedValues = new LinkedHashMap<Class<?>, Set<T>>();
		for(Class<?> type : types) {
			Set<T> list = null;
			if(classifiedValues.containsKey(type)) {
				list = this.classifiedValues.get(type);
			} else {
				list = new LinkedHashSet<T>();
				classifiedValues.put(type, list);
			}
			for(T value : values) {
				if (value == null) {
					if(SketchUtil.isPrimitiveOrBoxingType(type)) {
						continue;
					} else {
						list.add(value);
					}
				} else if(Reflection.canBeUsedAs(value.getClass(), type)) {
					list.add(value);
				}
			}
			
			Checker.checkTrue(!list.isEmpty(), "There is no value for type: " + type);
		}
		//generators
		this.generators = new ArrayList<IGenerator<T>>();
		for(Class<?> type : types) {
			IGenerator<T> chainGenerator = new ChainValueGenerator<T>(this.classifiedValues.get(type));
			this.generators.add(chainGenerator);
		}
	}
	
	@Override
	public void reset() {
		super.reset();
		for(IGenerator<T> generator : generators) {
			generator.reset();
		}
	}
	
	@Override
	public SequentialValue<T> generateCurrent() {
		List<T> generatedValues = new ArrayList<T>();
		for(int i = 0; i < generators.size(); i++) {
			IGenerator<T> generator = generators.get(i);
			T generatedValue = generator.current();
			
			Class<?> type = this.types[i];
			if(generatedValue != null) {
			    Checker.checkTrue(Reflection.canBeUsedAs(generatedValue.getClass(), type), "Type: "
			    		+ generatedValue.getClass() + " can not be used as: " + type);
			} else {
				Checker.checkTrue(!SketchUtil.isPrimitiveOrBoxingType(type), "Null is not applicable " +
						"for primitive type: " + type);
			}
			
			generatedValues.add(generatedValue);
		}
		
		SequentialValue<T> assembledValue = new SequentialValue<T>(generatedValues);
		return assembledValue;
	}

	@Override
	public IIterationLink getInitializedIterator() {
		return LinkCreator.createInOrderGenCombinatoricLink((IGenerator<T>[]) this.generators.toArray(new IGenerator[0]));
	}

}